package pkleczek.bugbrain.laboratory;

import java.util.Arrays;
import java.util.List;

import pkleczek.bugbrain.Laboratory.InsertionMode;
import pkleczek.bugbrain.R;
import pkleczek.bugbrain.helpers.Dimension;
import pkleczek.bugbrain.laboratory.elements.CircutElement;
import pkleczek.bugbrain.laboratory.elements.Element;
import pkleczek.bugbrain.laboratory.elements.Neuron;
import pkleczek.bugbrain.laboratory.elements.Node;
import pkleczek.bugbrain.laboratory.elements.Synapse;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

public class LaboratoryView extends View {

	/**
	 * Parameters controlling the size of the tiles and their range within view.
	 * Width/Height are in pixels, and Drawables will be scaled to fit to these
	 * dimensions. X/Y Tile Counts are the number of tiles that will be drawn.
	 */

	protected static int mTileSize;

	protected static int mXTileCount = 50;
	protected static int mYTileCount = 50;

	private static int mXOffset;
	private static int mYOffset;

	/**
	 * A hash that maps integer handles specified by the subclasser to the
	 * drawable that will be used for that reference
	 */
	private Bitmap[] mTileArray;

	/**
	 * A two-dimensional array of integers in which the number represents the
	 * index of the tile that should be drawn at that locations
	 */
	private int[][] mTileGrid;

	private final Paint mPaint = new Paint();
	private final Rect mRect = new Rect();
	private final Point mPoint = new Point();

	private final Workspace workspace = new Workspace(new Dimension(40, 20));

	private InsertionMode insertionMode = InsertionMode.NONE;
	private CircutElement synapseInput = null;

	public LaboratoryView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);

		TypedArray a = context.obtainStyledAttributes(attrs,
				R.styleable.LaboratoryView);

		mTileSize = a.getInt(R.styleable.LaboratoryView_tileSize, 12);

		a.recycle();
		debug();
	}

	public LaboratoryView(Context context, AttributeSet attrs) {
		super(context, attrs);

		TypedArray a = context.obtainStyledAttributes(attrs,
				R.styleable.LaboratoryView);

		mTileSize = a.getInt(R.styleable.LaboratoryView_tileSize, 20);

		a.recycle();
		debug();
	}

	private final int LONG_PRESS_TIMEOUT = ViewConfiguration
			.getLongPressTimeout();
	private Handler mHandler = new Handler();
	private boolean mIsLongPress = false;
	private boolean mWasLongPress = false;
	private Runnable longPress = new Runnable() {

		@Override
		public void run() {
			if (mIsLongPress) {
				actionOne();
				mIsLongPress = false;
				mWasLongPress = true;
			}
		}

	};

	private void actionOne() {
		AlertDialog.Builder builder = new AlertDialog.Builder(this.getContext());
		builder.setTitle("action").setItems(R.array.testArray,
				new DialogInterface.OnClickListener() {
					public void onClick(DialogInterface dialog, int which) {
						// The 'which' argument contains the index position
						// of the selected item
					}
				});
		AlertDialog dlg = builder.create();
		dlg.show();
	}

	private void clickAction(MotionEvent e) {
		System.out.println("Click!!");

		// TODO : sprawdzić pointery (czy naciskanie tylko jednym przyciskiem)
		// @see:
		// http://developer.android.com/reference/android/view/MotionEvent.html

		// TODO : uwzględnić przesunięcie (scroll)
		int x = (int) e.getX() / mTileSize;
		int y = (int) e.getY() / mTileSize;
		mPoint.set(x, y);
		System.out.println(String.format("[%d, %d]", x, y));

		switch (insertionMode) {
		case NEURON:
			workspace.addElement(Neuron.class, new Point(x, y));
			break;
		case NODE:
			workspace.addElement(Node.class, new Point(x, y));
			break;
		case SYNAPSE:
			addSynapse(mPoint);
			break;
		}

		if (insertionMode == InsertionMode.NONE && synapseInput != null) {
			addSynapse(mPoint);
			synapseInput = null;
		}

		// TODO : tymczasowo (nie uwzględnia wstawiania synapsy)
		insertionMode = InsertionMode.NONE;

		invalidate();
	}

	private void debug() {
		mWasLongPress = false;

		setOnTouchListener(new OnTouchListener() {

			@Override
			public boolean onTouch(View v, MotionEvent event) {
				final int action = event.getAction();
				switch (action) {
				case MotionEvent.ACTION_DOWN:
					mIsLongPress = true;
					mHandler.postDelayed(longPress, LONG_PRESS_TIMEOUT);
					break;
				case MotionEvent.ACTION_MOVE:
				case MotionEvent.ACTION_CANCEL:
				case MotionEvent.ACTION_UP:
					mIsLongPress = false;
					mHandler.removeCallbacks(longPress);

					if (!mWasLongPress) {
						clickAction(event);
					}
					mWasLongPress = false;
					break;
				default:
					System.out.println(action);
				}
				return true;
			}
		});

		// -- przykładowe elementy --

		Neuron n = (Neuron) workspace.addElement(Neuron.class, new Point(3, 5));
		Node node = (Node) workspace.addElement(Node.class, new Point(6, 9));

		Synapse s = new Synapse(node, n, Arrays.asList(new Point[] { new Point(
				n.getPosition().x, node.getPosition().y) }));
		workspace.addSynapse(s);
	}

	/**
	 * Rests the internal array of Bitmaps used for drawing tiles, and sets the
	 * maximum index of tiles to be inserted
	 * 
	 * @param tilecount
	 */
	public void resetTiles(int tilecount) {
		mTileArray = new Bitmap[tilecount];
	}

	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		// mXTileCount = (int) Math.floor(w / mTileSize);
		// mYTileCount = (int) Math.floor(h / mTileSize);
		//
		// mXOffset = ((w - (mTileSize * mXTileCount)) / 2);
		// mYOffset = ((h - (mTileSize * mYTileCount)) / 2);
		//
		// mTileGrid = new int[mXTileCount][mYTileCount];
		// clearTiles();
	}

	/**
	 * Function to set the specified Drawable as the tile for a particular
	 * integer key.
	 * 
	 * @param key
	 * @param tile
	 */
	public void loadTile(int key, Drawable tile) {
		Bitmap bitmap = Bitmap.createBitmap(mTileSize, mTileSize,
				Bitmap.Config.ARGB_8888);
		Canvas canvas = new Canvas(bitmap);
		tile.setBounds(0, 0, mTileSize, mTileSize);
		tile.draw(canvas);

		mTileArray[key] = bitmap;
	}

	/**
	 * Resets all tiles to 0 (empty)
	 * 
	 */
	public void clearTiles() {
		for (int x = 0; x < mXTileCount; x++) {
			for (int y = 0; y < mYTileCount; y++) {
				setTile(0, x, y);
			}
		}
	}

	/**
	 * Used to indicate that a particular tile (set with loadTile and referenced
	 * by an integer) should be drawn at the given x/y coordinates during the
	 * next invalidate/draw cycle.
	 * 
	 * @param tileindex
	 * @param x
	 * @param y
	 */
	public void setTile(int tileindex, int x, int y) {
		mTileGrid[x][y] = tileindex;
	}

	@Override
	public void onDraw(Canvas canvas) {
		super.onDraw(canvas);

		mPaint.setColor(Color.LTGRAY);
		mRect.set(0, 0, canvas.getWidth(), canvas.getHeight());
		canvas.drawRect(mRect, mPaint);

		mPaint.setColor(Color.DKGRAY);

		canvas.getClipBounds(mRect);

		int hTiles = mRect.width() / mTileSize;
		int vTiles = mRect.height() / mTileSize;

		// Draw grid
		for (int x = 0; x < hTiles; x++) {
			canvas.drawLine(x * mTileSize, 0, x * mTileSize, mRect.height(),
					mPaint);
		}
		for (int y = 0; y < vTiles; y++) {
			canvas.drawLine(0, y * mTileSize, mRect.width(), y * mTileSize,
					mPaint);
		}

		for (Synapse syn : workspace.getSynapses()) {
			syn.draw(canvas, mTileSize);
		}

		for (Element elem : workspace.getElements()) {
			elem.draw(canvas, mTileSize);
		}

	}

	private void addSynapse(Point pos) {

		/*
		 * Sprawdź, czy wśród zaznaczonych obiektów znajduje się CircutElement.
		 * Jeśli tak, sprawdź czy posiada wolne sloty na synapsy (jeśli nie -
		 * zrzuć); uwzględnianie 'synapseIutput'.
		 */

		List<Object> items = workspace.getClickedItems(pos);
		CircutElement elem = null;

		for (Object item : items) {
			if (CircutElement.class.isAssignableFrom(item.getClass()))
				elem = (CircutElement) item;
		}

		if (elem == null) {
			insertionMode = InsertionMode.NONE;
			synapseInput = null;
			return;
		}

		if (synapseInput == null) {
			synapseInput = elem;
		} else {
			Synapse s = new Synapse(synapseInput, elem,
					Arrays.asList(new Point[] { new Point(elem.getPosition().x,
							synapseInput.getPosition().y) }));
			workspace.addSynapse(s);
		}
	}

	public void clearWorkspace() {
		workspace.clear();
		invalidate();
	}

	public InsertionMode getInsertionMode() {
		return insertionMode;
	}

	public void setInsertionMode(InsertionMode insertionMode) {
		this.insertionMode = insertionMode;
	}
}
